/*************************************************************************
 *  Compilation:  javac RSA.java
 *  Execution:    java RSA N
 *  
 *  Generate an N-bit public and private RSA key and use to encrypt
 *  and decrypt a random message.
 * 
 *  % java RSA 50
 *  public  = 65537
 *  private = 553699199426609
 *  modulus = 825641896390631
 *  message   = 48194775244950
 *  encrpyted = 321340212160104
 *  decrypted = 48194775244950
 *
 *  Known bugs (not addressed for simplicity)
 *  -----------------------------------------
 *  - It could be the case that the message >= modulus. To avoid, use
 *    a do-while loop to generate key until modulus happen to be exactly N bits.
 *
 *  - It's possible that gcd(phi, publicKey) != 1 in which case
 *    the key generation fails. This will only happen if phi is a
 *    multiple of 65537. To avoid, use a do-while loop to generate
 *    keys until the gcd is 1.
 *
 *************************************************************************/

import java.math.BigInteger;
import java.security.SecureRandom;

public class RSA {

	private final static SecureRandom secureRandom = new SecureRandom();

	private BigInteger privateKey;
	private BigInteger publicKey;
	private BigInteger mod;
	
	RSA(String s) {
		int N = 0;
		for (int i = 0; i < s.length(); i++) {
			char c = s.charAt(i);
			int j = (int) c;
			N += j;
		}
		BigInteger p = BigInteger.probablePrime(N / 2, secureRandom);
		BigInteger q = BigInteger.probablePrime(N / 2, secureRandom);
		BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));

		mod = p.multiply(q);
		publicKey = new BigInteger("618749"); // common value = 2^16 + 1
		privateKey = publicKey.modInverse(phi);
	}

	RSA(int N) {
		BigInteger p = BigInteger.probablePrime(N / 2, secureRandom);
		BigInteger q = BigInteger.probablePrime(N / 2, secureRandom);
		BigInteger phi = (p.subtract(BigInteger.ONE)).multiply(q.subtract(BigInteger.ONE));

		mod = p.multiply(q);
		publicKey = new BigInteger("618749"); // common value = 2^16 + 1
		privateKey = publicKey.modInverse(phi);
	}
	
	BigInteger encrypt(String message) {
		byte[] bytes = message.getBytes();
		BigInteger bi = new BigInteger(bytes);
		return bi.modPow(publicKey, mod);
	}
	
	String decrypt(BigInteger encrypted) {
		byte[] bytes = encrypted.modPow(privateKey, mod).toByteArray();
		String out = new String(bytes);
		return out;
	}

	public String toString() {
		String s = "";
		s += "Public key  = " + publicKey + "\n";
		s += "Private key = " + privateKey + "\n";
		s += "Modulus = " + mod;
		return s;
	}
}